package com.swak.fx.support.ui;

import java.awt.image.BufferedImage;
import java.io.File;
import java.net.URL;
import java.util.List;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.events.EventTarget;
import org.w3c.dom.html.HTMLElement;

import com.sun.javafx.webkit.Accessor;
import com.sun.webkit.WebPage;
import com.swak.OS;
import com.swak.codec.Encodes;
import com.swak.fx.support.Display;
import com.swak.fx.support.FileChoosers;
import com.swak.fx.support.Snapshots;
import com.swak.fx.support.snapshot.FileSnapshot;
import com.swak.fx.support.ui.html.HtmlParser;
import com.swak.ui.Media;
import com.swak.utils.IOUtils;
import com.swak.utils.ImageUtils;
import com.swak.utils.StringUtils;

import javafx.beans.property.MapProperty;
import javafx.beans.property.SimpleMapProperty;
import javafx.collections.FXCollections;
import javafx.concurrent.Worker.State;
import javafx.scene.image.Image;
import javafx.scene.input.Clipboard;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.AnchorPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import lombok.Getter;
import netscape.javascript.JSObject;

/**
 * UIRichText.
 * 记录：只有 png 格式的图片才能是用 base64 的显示；所以在做base64处理时需要先转为 png；
 * 
 * @author 618lf
 */
public class UIRichText extends AnchorPane implements UILifecycle {

	private static final Logger LOGGER = LoggerFactory.getLogger(UIRichText.class);
	private final String STYLE_CLASS = "ui-richtext";
	private final String STYLESHEET = UI.load("css/UIRichText.css");
	private final URL HTML = UI.loadURL("html/UIRichText.html");
//	private final URL JS1 = UI.loadURL("html/richtext/index.js");
//	private final URL JS2 = UI.loadURL("html/richtext/init.js");
	@Getter
	private final WebView webView;
	@Getter
	private final WebPage webPage;
	@Getter
	private final WebEngine webEngine;

	public UIRichText() {
		this.webView = new WebView();
		this.webEngine = webView.getEngine();
		this.webPage = Accessor.getPageFor(webView.getEngine());
		this.init();
	}

	@Override
	public void initializeParts() {
		getStyleClass().add(STYLE_CLASS);
		this.webView.setFocusTraversable(true);
		this.webView.setContextMenuEnabled(false);
		this.webPage.setEditable(false);
		this.load();
		this.getChildren().add(webView);
		AnchorPane.setBottomAnchor(webView, 0.0);
		AnchorPane.setLeftAnchor(webView, 0.0);
		AnchorPane.setTopAnchor(webView, 0.0);
		AnchorPane.setRightAnchor(webView, 0.0);
	}

	@Override
	public void setupValueChangedListeners() {
		this.focusedProperty().addListener((ob, o, n) -> {
			if (n) {
				this.webView.requestFocus();
			}
		});
	}

	@Override
	public void setupEventHandlers() {
		this.webView.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
			if (e.getCode() == KeyCode.V && e.isControlDown()) {
				this.doCopy(e);
			}
		});
		this.webView.setOnDragOver((e) -> {
			e.acceptTransferModes(TransferMode.COPY);
			this.webView.requestFocus();
		});
		this.webView.setOnDragDropped((e) -> {
			TransferMode mode = e.getAcceptedTransferMode();
			if (mode == TransferMode.COPY) {
				e.setDropCompleted(true);
				this.doDragCopy(e.getDragboard());
			}
		});
	}

	private void doCopy(KeyEvent event) {
		event.consume();
		this.doDragCopy(Clipboard.getSystemClipboard());
	}

	private void doDragCopy(Clipboard clipboard) {
		if (StringUtils.isNotBlank(clipboard.getHtml())) {
			this.doDragCopyForHtml(clipboard.getHtml());
		} else if (StringUtils.isNotBlank(clipboard.getString())) {
			this.doDragCopyForText(clipboard.getString());
		} else if (clipboard.getImage() != null) {
			this.doDragCopyForImage(clipboard.getImage());
		} else if (clipboard.getFiles() != null) {
			this.doDragCopyForFiles(clipboard.getFiles());
		}
	}

	private void doDragCopyForImage(Image file) {
		if (file != null) {
			BufferedImage bufferedImage = Snapshots.fromFXImage(file, null);
			String image = ImageUtils.toBase64(bufferedImage);
			String base64Image = StringUtils.format("<img src=\"data:image/png;base64,%s\"/>", image);
			this.insertHtmlUnEncode(base64Image);
		}
	}

	private void doDragCopyForFiles(List<File> files) {
		if (files != null && !files.isEmpty()) {
			for (File file : files) {
				Media media = Media.of(file.getName());
				if (media == Media.Image) {
					this.doDragCopyForImage(file, file);
				} else {
					this.doDragCopyForFile(file);
				}
			}
		}
	}

	private void doDragCopyForImage(File snapshot, File realFile) {
		String id = UUID.randomUUID().toString();
		this.filesProperty.put(id, realFile.getPath());
		String image = ImageUtils.toBase64(snapshot);
		String base64Image = StringUtils.format("<img data-href=\"%s\" src=\"data:image/png;base64,%s\"/>", id, image);
		this.insertHtmlUnEncode(base64Image);
	}

	private void doDragCopyForFile(File file) {
		FileSnapshot snapshot = FileSnapshot.of(file);
		this.getChildren().add(snapshot.getRoot());
		File image = snapshot.output();
		this.getChildren().remove(snapshot.getRoot());
		this.doDragCopyForImage(image, file);
	}

	private void doDragCopyForText(String text) {
		if (StringUtils.isNotBlank(text)) {
			this.insertText(text);
		}
	}

	private void doDragCopyForHtml(String html) {
		if (StringUtils.isNotBlank(html)) {
			this.parseHtml(html);
		}
	}

	private void parseHtml(String html) {
		String parsed = HtmlParser.parse(html);
		this.insertHtml(parsed);
	}

	@Override
	public void initializeFinish() {
		if (this.webEngine.getLoadWorker().stateProperty().get() == State.SUCCEEDED) {
			this.onWebEngineLoadSuccess();
		} else {
			this.webEngine.getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> {
				if (newValue == State.SUCCEEDED) {
					this.onWebEngineLoadSuccess();
				}
			});
		}
	}

	// ================================================================================
	// Listeners: $App 必须是 public、且需要定义为属性（不要修改$App的定义和申明）
	// ================================================================================
	private final $App app = new $App();

	private void onWebEngineLoadSuccess() {

		// 可以通过此方式异步加载一些js
		// this.webEngine.executeScript(this.loadEditorJs(JS1));
		// this.webEngine.executeScript(this.loadEditorJs(JS2));

		// register app
		JSObject win = (JSObject) this.webEngine.executeScript("window");
		win.setMember("$App", app);

		// monitor event
		Document doc = webEngine.getDocument();
		((EventTarget) doc).addEventListener("dblclick", (org.w3c.dom.events.Event evt) -> {
			evt.stopPropagation();
			EventTarget target = evt.getTarget();
			HTMLElement element = null;
			if (target == null || (element = target instanceof HTMLElement ? (HTMLElement) target : null) == null
					|| element.getNodeName().equals("BODY") || element.getNodeName().equals("HTML")) {
				return;
			}

			// 预览文件
			this.previewFile(element);
		}, true);
	}

	public class $App {
		public void openFile() {
			UIRichText.this.openFile();
		}

		public void log(String log) {
			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug("Log - {}", log);
			}
		}
	}

	void openFile() {
		Display.runUI(() -> {
			List<File> files = FileChoosers.openFileChooser(this.getScene().getWindow());
			if (files != null && !files.isEmpty()) {
				this.doDragCopyForFiles(files);
			}
		});
	}

	void previewFile(HTMLElement element) {
		String fileId = element.getAttribute("data-href");
		if (StringUtils.isBlank(fileId)) {
			NodeList images = element.getChildNodes();
			for (int i = 0; i < images.getLength(); i++) {
				Node image = images.item(i);
				if (image instanceof Element) {
					fileId = ((Element) image).getAttribute("data-href");
				}
			}
		}

		// 打开文件
		String file = this.filesProperty.get(fileId);
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("[打开文件]: {}", file);
		}
		if (StringUtils.isNotBlank(file)) {
			OS.open(file);
		}
	}

	// ================================================================================
	// Function
	// ================================================================================

	/**
	 * 重新加载 Html 内容， 
	 *  
	 * @param html
	 */
	public final void load() {
		String html = this.loadEditorHtml();
		this.webPage.load(webPage.getMainFrame(), html, "text/html");
	}

	protected String loadEditorHtml() {
		try {
			return IOUtils.readAsText(HTML);
		} catch (Exception e) {
			throw new IllegalArgumentException(e);
		}
	}

	protected String loadEditorJs(URL url) {
		try {
			return IOUtils.readAsText(url);
		} catch (Exception e) {
			throw new IllegalArgumentException(e);
		}
	}

	// ================================================================================
	// Overridden Methods
	// ================================================================================

	@Override
	public String getUserAgentStylesheet() {
		return STYLESHEET;
	}

	// ================================================================================
	// Api
	// ================================================================================

	/**
	 * 获得所有的 Html 信息
	 * 
	 * @return
	 */
	public final String getHtml() {
		try {
			return String.valueOf(this.webEngine.executeScript("GetHtml()"));
		} catch (Exception e) {
			return StringUtils.EMPTY;
		}
	}

	/**
	 * 获得所有的 Html 信息
	 * 
	 * @return
	 */
	public final String getText() {
		try {
			return String.valueOf(this.webEngine.executeScript("GetText()"));
		} catch (Exception e) {
			return StringUtils.EMPTY;
		}
	}

	/**
	 * 获得所有的 Html 信息
	 * 
	 * @return
	 */
	public final String getSelectionText() {
		try {
			return String.valueOf(this.webEngine.executeScript("GetSelectionText()"));
		} catch (Exception e) {
			return StringUtils.EMPTY;
		}
	}

	/**
	 * 获得所有的 Html 信息
	 * 
	 * @return
	 */
	public final void setHtml(String html) {
		try {
			this.webEngine.executeScript(StringUtils.format("SetHtml('%s')", Encodes.urlEncode(html)));
		} catch (Exception e) {
		}
	}

	/**
	 * 追加 Html
	 *  
	 * @param html
	 */
	public final void insertHtml(String html) {
		try {
			this.webEngine.executeScript(StringUtils.format("InsertHtml('%s')", Encodes.urlEncode(html)));
		} catch (Exception e) {
		}
	}

	/**
	 * 追加 Html
	 *  
	 * @param html
	 */
	public final void insertHtmlUnEncode(String html) {
		try {
			this.webEngine.executeScript(StringUtils.format("InsertHtml('%s')", html));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 追加 Text
	 *  
	 * @param html
	 */
	public final void insertText(String html) {
		try {
			this.webEngine.executeScript(StringUtils.format("InsertText('%s')", Encodes.urlEncode(html)));
		} catch (Exception e) {
		}
	}

	// ================================================================================
	// Properties
	// ================================================================================
	private final MapProperty<String, String> filesProperty = new SimpleMapProperty<>(
			FXCollections.observableHashMap());

	public MapProperty<String, String> filesProperty() {
		return filesProperty;
	}
}
